/*! Buttons for DataTables 1.5.2
 * ©2016-2018 SpryMedia Ltd - datatables.net/license
 */

(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery', 'datatables.net'], function ($) {
            return factory($, window, document);
        });
    }
    else if (typeof exports === 'object') {
        // CommonJS
        module.exports = function (root, $) {
            if (!root) {
                root = window;
            }

            if (!$ || !$.fn.dataTable) {
                $ = require('datatables.net')(root, $).$;
            }

            return factory($, root, root.document);
        };
    }
    else {
        // Browser
        factory(jQuery, window, document);
    }
}(function ($, window, document, undefined) {
    'use strict';
    var DataTable = $.fn.dataTable;


// Used for namespacing events added to the document by each instance, so they
// can be removed on destroy
    var _instCounter = 0;

// Button namespacing counter for namespacing events on individual buttons
    var _buttonCounter = 0;

    var _dtButtons = DataTable.ext.buttons;

    /**
     * [Buttons description]
     * @param {[type]}
     * @param {[type]}
     */
    var Buttons = function (dt, config) {
        // If there is no config set it to an empty object
        if (typeof( config ) === 'undefined') {
            config = {};
        }

        // Allow a boolean true for defaults
        if (config === true) {
            config = {};
        }

        // For easy configuration of buttons an array can be given
        if ($.isArray(config)) {
            config = {buttons: config};
        }

        this.c = $.extend(true, {}, Buttons.defaults, config);

        // Don't want a deep copy for the buttons
        if (config.buttons) {
            this.c.buttons = config.buttons;
        }

        this.s = {
            dt: new DataTable.Api(dt),
            buttons: [],
            listenKeys: '',
            namespace: 'dtb' + (_instCounter++)
        };

        this.dom = {
            container: $('<' + this.c.dom.container.tag + '/>')
                .addClass(this.c.dom.container.className)
        };

        this._constructor();
    };


    $.extend(Buttons.prototype, {
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
         * Public methods
         */

        /**
         * Get the action of a button
         * @param  {int|string} Button index
         * @return {function}
         *//**
         * Set the action of a button
         * @param  {node} node Button element
         * @param  {function} action Function to set
         * @return {Buttons} Self for chaining
         */
        action: function (node, action) {
            var button = this._nodeToButton(node);

            if (action === undefined) {
                return button.conf.action;
            }

            button.conf.action = action;

            return this;
        },

        /**
         * Add an active class to the button to make to look active or get current
         * active state.
         * @param  {node} node Button element
         * @param  {boolean} [flag] Enable / disable flag
         * @return {Buttons} Self for chaining or boolean for getter
         */
        active: function (node, flag) {
            var button = this._nodeToButton(node);
            var klass = this.c.dom.button.active;
            var jqNode = $(button.node);

            if (flag === undefined) {
                return jqNode.hasClass(klass);
            }

            jqNode.toggleClass(klass, flag === undefined ? true : flag);

            return this;
        },

        /**
         * Add a new button
         * @param {object} config Button configuration object, base string name or function
         * @param {int|string} [idx] Button index for where to insert the button
         * @return {Buttons} Self for chaining
         */
        add: function (config, idx) {
            var buttons = this.s.buttons;

            if (typeof idx === 'string') {
                var split = idx.split('-');
                var base = this.s;

                for (var i = 0, ien = split.length - 1; i < ien; i++) {
                    base = base.buttons[split[i] * 1];
                }

                buttons = base.buttons;
                idx = split[split.length - 1] * 1;
            }

            this._expandButton(buttons, config, false, idx);
            this._draw();

            return this;
        },

        /**
         * Get the container node for the buttons
         * @return {jQuery} Buttons node
         */
        container: function () {
            return this.dom.container;
        },

        /**
         * Disable a button
         * @param  {node} node Button node
         * @return {Buttons} Self for chaining
         */
        disable: function (node) {
            var button = this._nodeToButton(node);

            $(button.node).addClass(this.c.dom.button.disabled);

            return this;
        },

        /**
         * Destroy the instance, cleaning up event handlers and removing DOM
         * elements
         * @return {Buttons} Self for chaining
         */
        destroy: function () {
            // Key event listener
            $('body').off('keyup.' + this.s.namespace);

            // Individual button destroy (so they can remove their own events if
            // needed). Take a copy as the array is modified by `remove`
            var buttons = this.s.buttons.slice();
            var i, ien;

            for (i = 0, ien = buttons.length; i < ien; i++) {
                this.remove(buttons[i].node);
            }

            // Container
            this.dom.container.remove();

            // Remove from the settings object collection
            var buttonInsts = this.s.dt.settings()[0];

            for (i = 0, ien = buttonInsts.length; i < ien; i++) {
                if (buttonInsts.inst === this) {
                    buttonInsts.splice(i, 1);
                    break;
                }
            }

            return this;
        },

        /**
         * Enable / disable a button
         * @param  {node} node Button node
         * @param  {boolean} [flag=true] Enable / disable flag
         * @return {Buttons} Self for chaining
         */
        enable: function (node, flag) {
            if (flag === false) {
                return this.disable(node);
            }

            var button = this._nodeToButton(node);
            $(button.node).removeClass(this.c.dom.button.disabled);

            return this;
        },

        /**
         * Get the instance name for the button set selector
         * @return {string} Instance name
         */
        name: function () {
            return this.c.name;
        },

        /**
         * Get a button's node
         * @param  {node} node Button node
         * @return {jQuery} Button element
         */
        node: function (node) {
            var button = this._nodeToButton(node);
            return $(button.node);
        },

        /**
         * Set / get a processing class on the selected button
         * @param  {boolean} flag true to add, false to remove, undefined to get
         * @return {boolean|Buttons} Getter value or this if a setter.
         */
        processing: function (node, flag) {
            var button = this._nodeToButton(node);

            if (flag === undefined) {
                return $(button.node).hasClass('processing');
            }

            $(button.node).toggleClass('processing', flag);

            return this;
        },

        /**
         * Remove a button.
         * @param  {node} node Button node
         * @return {Buttons} Self for chaining
         */
        remove: function (node) {
            var button = this._nodeToButton(node);
            var host = this._nodeToHost(node);
            var dt = this.s.dt;

            // Remove any child buttons first
            if (button.buttons.length) {
                for (var i = button.buttons.length - 1; i >= 0; i--) {
                    this.remove(button.buttons[i].node);
                }
            }

            // Allow the button to remove event handlers, etc
            if (button.conf.destroy) {
                button.conf.destroy.call(dt.button(node), dt, $(node), button.conf);
            }

            this._removeKey(button.conf);

            $(button.node).remove();

            var idx = $.inArray(button, host);
            host.splice(idx, 1);

            return this;
        },

        /**
         * Get the text for a button
         * @param  {int|string} node Button index
         * @return {string} Button text
         *//**
         * Set the text for a button
         * @param  {int|string|function} node Button index
         * @param  {string} label Text
         * @return {Buttons} Self for chaining
         */
        text: function (node, label) {
            var button = this._nodeToButton(node);
            var buttonLiner = this.c.dom.collection.buttonLiner;
            var linerTag = button.inCollection && buttonLiner && buttonLiner.tag ?
                buttonLiner.tag :
                this.c.dom.buttonLiner.tag;
            var dt = this.s.dt;
            var jqNode = $(button.node);
            var text = function (opt) {
                return typeof opt === 'function' ?
                    opt(dt, jqNode, button.conf) :
                    opt;
            };

            if (label === undefined) {
                return text(button.conf.text);
            }

            button.conf.text = label;

            if (linerTag) {
                jqNode.children(linerTag).html(text(label));
            }
            else {
                jqNode.html(text(label));
            }

            return this;
        },


        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
         * Constructor
         */

        /**
         * Buttons constructor
         * @private
         */
        _constructor: function () {
            var that = this;
            var dt = this.s.dt;
            var dtSettings = dt.settings()[0];
            var buttons = this.c.buttons;

            if (!dtSettings._buttons) {
                dtSettings._buttons = [];
            }

            dtSettings._buttons.push({
                inst: this,
                name: this.c.name
            });

            for (var i = 0, ien = buttons.length; i < ien; i++) {
                this.add(buttons[i]);
            }

            dt.on('destroy', function (e, settings) {
                if (settings === dtSettings) {
                    that.destroy();
                }
            });

            // Global key event binding to listen for button keys
            $('body').on('keyup.' + this.s.namespace, function (e) {
                if (!document.activeElement || document.activeElement === document.body) {
                    // SUse a string of characters for fast lookup of if we need to
                    // handle this
                    var character = String.fromCharCode(e.keyCode).toLowerCase();

                    if (that.s.listenKeys.toLowerCase().indexOf(character) !== -1) {
                        that._keypress(character, e);
                    }
                }
            });
        },


        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
         * Private methods
         */

        /**
         * Add a new button to the key press listener
         * @param {object} conf Resolved button configuration object
         * @private
         */
        _addKey: function (conf) {
            if (conf.key) {
                this.s.listenKeys += $.isPlainObject(conf.key) ?
                    conf.key.key :
                    conf.key;
            }
        },

        /**
         * Insert the buttons into the container. Call without parameters!
         * @param  {node} [container] Recursive only - Insert point
         * @param  {array} [buttons] Recursive only - Buttons array
         * @private
         */
        _draw: function (container, buttons) {
            if (!container) {
                container = this.dom.container;
                buttons = this.s.buttons;
            }

            container.children().detach();

            for (var i = 0, ien = buttons.length; i < ien; i++) {
                container.append(buttons[i].inserter);
                container.append(' ');

                if (buttons[i].buttons && buttons[i].buttons.length) {
                    this._draw(buttons[i].collection, buttons[i].buttons);
                }
            }
        },

        /**
         * Create buttons from an array of buttons
         * @param  {array} attachTo Buttons array to attach to
         * @param  {object} button Button definition
         * @param  {boolean} inCollection true if the button is in a collection
         * @private
         */
        _expandButton: function (attachTo, button, inCollection, attachPoint) {
            var dt = this.s.dt;
            var buttonCounter = 0;
            var buttons = !$.isArray(button) ?
                [button] :
                button;

            for (var i = 0, ien = buttons.length; i < ien; i++) {
                var conf = this._resolveExtends(buttons[i]);

                if (!conf) {
                    continue;
                }

                // If the configuration is an array, then expand the buttons at this
                // point
                if ($.isArray(conf)) {
                    this._expandButton(attachTo, conf, inCollection, attachPoint);
                    continue;
                }

                var built = this._buildButton(conf, inCollection);
                if (!built) {
                    continue;
                }

                if (attachPoint !== undefined) {
                    attachTo.splice(attachPoint, 0, built);
                    attachPoint++;
                }
                else {
                    attachTo.push(built);
                }

                if (built.conf.buttons) {
                    var collectionDom = this.c.dom.collection;
                    built.collection = $('<' + collectionDom.tag + '/>')
                        .addClass(collectionDom.className)
                        .attr('role', 'menu');
                    built.conf._collection = built.collection;

                    this._expandButton(built.buttons, built.conf.buttons, true, attachPoint);
                }

                // init call is made here, rather than buildButton as it needs to
                // be selectable, and for that it needs to be in the buttons array
                if (conf.init) {
                    conf.init.call(dt.button(built.node), dt, $(built.node), conf);
                }

                buttonCounter++;
            }
        },

        /**
         * Create an individual button
         * @param  {object} config            Resolved button configuration
         * @param  {boolean} inCollection `true` if a collection button
         * @return {jQuery} Created button node (jQuery)
         * @private
         */
        _buildButton: function (config, inCollection) {
            var buttonDom = this.c.dom.button;
            var linerDom = this.c.dom.buttonLiner;
            var collectionDom = this.c.dom.collection;
            var dt = this.s.dt;
            var text = function (opt) {
                return typeof opt === 'function' ?
                    opt(dt, button, config) :
                    opt;
            };

            if (inCollection && collectionDom.button) {
                buttonDom = collectionDom.button;
            }

            if (inCollection && collectionDom.buttonLiner) {
                linerDom = collectionDom.buttonLiner;
            }

            // Make sure that the button is available based on whatever requirements
            // it has. For example, Flash buttons require Flash
            if (config.available && !config.available(dt, config)) {
                return false;
            }

            var action = function (e, dt, button, config) {
                config.action.call(dt.button(button), e, dt, button, config);

                $(dt.table().node()).triggerHandler('buttons-action.dt', [
                    dt.button(button), dt, button, config
                ]);
            };

            var tag = config.tag || buttonDom.tag;
            var button = $('<' + tag + '/>')
                .addClass(buttonDom.className)
                .attr('tabindex', this.s.dt.settings()[0].iTabIndex)
                .attr('aria-controls', this.s.dt.table().node().id)
                .on('click.dtb', function (e) {
                    e.preventDefault();

                    if (!button.hasClass(buttonDom.disabled) && config.action) {
                        action(e, dt, button, config);
                    }

                    button.blur();
                })
                .on('keyup.dtb', function (e) {
                    if (e.keyCode === 13) {
                        if (!button.hasClass(buttonDom.disabled) && config.action) {
                            action(e, dt, button, config);
                        }
                    }
                });

            // Make `a` tags act like a link
            if (tag.toLowerCase() === 'a') {
                button.attr('href', '#');
            }

            // Button tags should have `type=button` so they don't have any default behaviour
            if (tag.toLowerCase() === 'button') {
                button.attr('type', 'button');
            }

            if (linerDom.tag) {
                var liner = $('<' + linerDom.tag + '/>')
                    .html(text(config.text))
                    .addClass(linerDom.className);

                if (linerDom.tag.toLowerCase() === 'a') {
                    liner.attr('href', '#');
                }

                button.append(liner);
            }
            else {
                button.html(text(config.text));
            }

            if (config.enabled === false) {
                button.addClass(buttonDom.disabled);
            }

            if (config.className) {
                button.addClass(config.className);
            }

            if (config.titleAttr) {
                button.attr('title', text(config.titleAttr));
            }

            if (config.attr) {
                button.attr(config.attr);
            }

            if (!config.namespace) {
                config.namespace = '.dt-button-' + (_buttonCounter++);
            }

            var buttonContainer = this.c.dom.buttonContainer;
            var inserter;
            if (buttonContainer && buttonContainer.tag) {
                inserter = $('<' + buttonContainer.tag + '/>')
                    .addClass(buttonContainer.className)
                    .append(button);
            }
            else {
                inserter = button;
            }

            this._addKey(config);

            return {
                conf: config,
                node: button.get(0),
                inserter: inserter,
                buttons: [],
                inCollection: inCollection,
                collection: null
            };
        },

        /**
         * Get the button object from a node (recursive)
         * @param  {node} node Button node
         * @param  {array} [buttons] Button array, uses base if not defined
         * @return {object} Button object
         * @private
         */
        _nodeToButton: function (node, buttons) {
            if (!buttons) {
                buttons = this.s.buttons;
            }

            for (var i = 0, ien = buttons.length; i < ien; i++) {
                if (buttons[i].node === node) {
                    return buttons[i];
                }

                if (buttons[i].buttons.length) {
                    var ret = this._nodeToButton(node, buttons[i].buttons);

                    if (ret) {
                        return ret;
                    }
                }
            }
        },

        /**
         * Get container array for a button from a button node (recursive)
         * @param  {node} node Button node
         * @param  {array} [buttons] Button array, uses base if not defined
         * @return {array} Button's host array
         * @private
         */
        _nodeToHost: function (node, buttons) {
            if (!buttons) {
                buttons = this.s.buttons;
            }

            for (var i = 0, ien = buttons.length; i < ien; i++) {
                if (buttons[i].node === node) {
                    return buttons;
                }

                if (buttons[i].buttons.length) {
                    var ret = this._nodeToHost(node, buttons[i].buttons);

                    if (ret) {
                        return ret;
                    }
                }
            }
        },

        /**
         * Handle a key press - determine if any button's key configured matches
         * what was typed and trigger the action if so.
         * @param  {string} character The character pressed
         * @param  {object} e Key event that triggered this call
         * @private
         */
        _keypress: function (character, e) {
            // Check if this button press already activated on another instance of Buttons
            if (e._buttonsHandled) {
                return;
            }

            var run = function (conf, node) {
                if (!conf.key) {
                    return;
                }

                if (conf.key === character) {
                    e._buttonsHandled = true;
                    $(node).click();
                }
                else if ($.isPlainObject(conf.key)) {
                    if (conf.key.key !== character) {
                        return;
                    }

                    if (conf.key.shiftKey && !e.shiftKey) {
                        return;
                    }

                    if (conf.key.altKey && !e.altKey) {
                        return;
                    }

                    if (conf.key.ctrlKey && !e.ctrlKey) {
                        return;
                    }

                    if (conf.key.metaKey && !e.metaKey) {
                        return;
                    }

                    // Made it this far - it is good
                    e._buttonsHandled = true;
                    $(node).click();
                }
            };

            var recurse = function (a) {
                for (var i = 0, ien = a.length; i < ien; i++) {
                    run(a[i].conf, a[i].node);

                    if (a[i].buttons.length) {
                        recurse(a[i].buttons);
                    }
                }
            };

            recurse(this.s.buttons);
        },

        /**
         * Remove a key from the key listener for this instance (to be used when a
         * button is removed)
         * @param  {object} conf Button configuration
         * @private
         */
        _removeKey: function (conf) {
            if (conf.key) {
                var character = $.isPlainObject(conf.key) ?
                    conf.key.key :
                    conf.key;

                // Remove only one character, as multiple buttons could have the
                // same listening key
                var a = this.s.listenKeys.split('');
                var idx = $.inArray(character, a);
                a.splice(idx, 1);
                this.s.listenKeys = a.join('');
            }
        },

        /**
         * Resolve a button configuration
         * @param  {string|function|object} conf Button config to resolve
         * @return {object} Button configuration
         * @private
         */
        _resolveExtends: function (conf) {
            var dt = this.s.dt;
            var i, ien;
            var toConfObject = function (base) {
                var loop = 0;

                // Loop until we have resolved to a button configuration, or an
                // array of button configurations (which will be iterated
                // separately)
                while (!$.isPlainObject(base) && !$.isArray(base)) {
                    if (base === undefined) {
                        return;
                    }

                    if (typeof base === 'function') {
                        base = base(dt, conf);

                        if (!base) {
                            return false;
                        }
                    }
                    else if (typeof base === 'string') {
                        if (!_dtButtons[base]) {
                            throw 'Unknown button type: ' + base;
                        }

                        base = _dtButtons[base];
                    }

                    loop++;
                    if (loop > 30) {
                        // Protect against misconfiguration killing the browser
                        throw 'Buttons: Too many iterations';
                    }
                }

                return $.isArray(base) ?
                    base :
                    $.extend({}, base);
            };

            conf = toConfObject(conf);

            while (conf && conf.extend) {
                // Use `toConfObject` in case the button definition being extended
                // is itself a string or a function
                if (!_dtButtons[conf.extend]) {
                    throw 'Cannot extend unknown button type: ' + conf.extend;
                }

                var objArray = toConfObject(_dtButtons[conf.extend]);
                if ($.isArray(objArray)) {
                    return objArray;
                }
                else if (!objArray) {
                    // This is a little brutal as it might be possible to have a
                    // valid button without the extend, but if there is no extend
                    // then the host button would be acting in an undefined state
                    return false;
                }

                // Stash the current class name
                var originalClassName = objArray.className;

                conf = $.extend({}, objArray, conf);

                // The extend will have overwritten the original class name if the
                // `conf` object also assigned a class, but we want to concatenate
                // them so they are list that is combined from all extended buttons
                if (originalClassName && conf.className !== originalClassName) {
                    conf.className = originalClassName + ' ' + conf.className;
                }

                // Buttons to be added to a collection  -gives the ability to define
                // if buttons should be added to the start or end of a collection
                var postfixButtons = conf.postfixButtons;
                if (postfixButtons) {
                    if (!conf.buttons) {
                        conf.buttons = [];
                    }

                    for (i = 0, ien = postfixButtons.length; i < ien; i++) {
                        conf.buttons.push(postfixButtons[i]);
                    }

                    conf.postfixButtons = null;
                }

                var prefixButtons = conf.prefixButtons;
                if (prefixButtons) {
                    if (!conf.buttons) {
                        conf.buttons = [];
                    }

                    for (i = 0, ien = prefixButtons.length; i < ien; i++) {
                        conf.buttons.splice(i, 0, prefixButtons[i]);
                    }

                    conf.prefixButtons = null;
                }

                // Although we want the `conf` object to overwrite almost all of
                // the properties of the object being extended, the `extend`
                // property should come from the object being extended
                conf.extend = objArray.extend;
            }

            return conf;
        }
    });


    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * Statics
     */

    /**
     * Show / hide a background layer behind a collection
     * @param  {boolean} Flag to indicate if the background should be shown or
     *   hidden
     * @param  {string} Class to assign to the background
     * @static
     */
    Buttons.background = function (show, className, fade) {
        if (fade === undefined) {
            fade = 400;
        }

        if (show) {
            $('<div/>')
                .addClass(className)
                .css('display', 'none')
                .appendTo('body')
                .fadeIn(fade);
        }
        else {
            $('body > div.' + className)
                .fadeOut(fade, function () {
                    $(this)
                        .removeClass(className)
                        .remove();
                });
        }
    };

    /**
     * Instance selector - select Buttons instances based on an instance selector
     * value from the buttons assigned to a DataTable. This is only useful if
     * multiple instances are attached to a DataTable.
     * @param  {string|int|array} Instance selector - see `instance-selector`
     *   documentation on the DataTables site
     * @param  {array} Button instance array that was attached to the DataTables
     *   settings object
     * @return {array} Buttons instances
     * @static
     */
    Buttons.instanceSelector = function (group, buttons) {
        if (!group) {
            return $.map(buttons, function (v) {
                return v.inst;
            });
        }

        var ret = [];
        var names = $.map(buttons, function (v) {
            return v.name;
        });

        // Flatten the group selector into an array of single options
        var process = function (input) {
            if ($.isArray(input)) {
                for (var i = 0, ien = input.length; i < ien; i++) {
                    process(input[i]);
                }
                return;
            }

            if (typeof input === 'string') {
                if (input.indexOf(',') !== -1) {
                    // String selector, list of names
                    process(input.split(','));
                }
                else {
                    // String selector individual name
                    var idx = $.inArray($.trim(input), names);

                    if (idx !== -1) {
                        ret.push(buttons[idx].inst);
                    }
                }
            }
            else if (typeof input === 'number') {
                // Index selector
                ret.push(buttons[input].inst);
            }
        };

        process(group);

        return ret;
    };

    /**
     * Button selector - select one or more buttons from a selector input so some
     * operation can be performed on them.
     * @param  {array} Button instances array that the selector should operate on
     * @param  {string|int|node|jQuery|array} Button selector - see
     *   `button-selector` documentation on the DataTables site
     * @return {array} Array of objects containing `inst` and `idx` properties of
     *   the selected buttons so you know which instance each button belongs to.
     * @static
     */
    Buttons.buttonSelector = function (insts, selector) {
        var ret = [];
        var nodeBuilder = function (a, buttons, baseIdx) {
            var button;
            var idx;

            for (var i = 0, ien = buttons.length; i < ien; i++) {
                button = buttons[i];

                if (button) {
                    idx = baseIdx !== undefined ?
                        baseIdx + i :
                        i + '';

                    a.push({
                        node: button.node,
                        name: button.conf.name,
                        idx: idx
                    });

                    if (button.buttons) {
                        nodeBuilder(a, button.buttons, idx + '-');
                    }
                }
            }
        };

        var run = function (selector, inst) {
            var i, ien;
            var buttons = [];
            nodeBuilder(buttons, inst.s.buttons);

            var nodes = $.map(buttons, function (v) {
                return v.node;
            });

            if ($.isArray(selector) || selector instanceof $) {
                for (i = 0, ien = selector.length; i < ien; i++) {
                    run(selector[i], inst);
                }
                return;
            }

            if (selector === null || selector === undefined || selector === '*') {
                // Select all
                for (i = 0, ien = buttons.length; i < ien; i++) {
                    ret.push({
                        inst: inst,
                        node: buttons[i].node
                    });
                }
            }
            else if (typeof selector === 'number') {
                // Main button index selector
                ret.push({
                    inst: inst,
                    node: inst.s.buttons[selector].node
                });
            }
            else if (typeof selector === 'string') {
                if (selector.indexOf(',') !== -1) {
                    // Split
                    var a = selector.split(',');

                    for (i = 0, ien = a.length; i < ien; i++) {
                        run($.trim(a[i]), inst);
                    }
                }
                else if (selector.match(/^\d+(\-\d+)*$/)) {
                    // Sub-button index selector
                    var indexes = $.map(buttons, function (v) {
                        return v.idx;
                    });

                    ret.push({
                        inst: inst,
                        node: buttons[$.inArray(selector, indexes)].node
                    });
                }
                else if (selector.indexOf(':name') !== -1) {
                    // Button name selector
                    var name = selector.replace(':name', '');

                    for (i = 0, ien = buttons.length; i < ien; i++) {
                        if (buttons[i].name === name) {
                            ret.push({
                                inst: inst,
                                node: buttons[i].node
                            });
                        }
                    }
                }
                else {
                    // jQuery selector on the nodes
                    $(nodes).filter(selector).each(function () {
                        ret.push({
                            inst: inst,
                            node: this
                        });
                    });
                }
            }
            else if (typeof selector === 'object' && selector.nodeName) {
                // Node selector
                var idx = $.inArray(selector, nodes);

                if (idx !== -1) {
                    ret.push({
                        inst: inst,
                        node: nodes[idx]
                    });
                }
            }
        };


        for (var i = 0, ien = insts.length; i < ien; i++) {
            var inst = insts[i];

            run(selector, inst);
        }

        return ret;
    };


    /**
     * Buttons defaults. For full documentation, please refer to the docs/option
     * directory or the DataTables site.
     * @type {Object}
     * @static
     */
    Buttons.defaults = {
        buttons: ['copy', 'excel', 'csv', 'pdf', 'print'],
        name: 'main',
        tabIndex: 0,
        dom: {
            container: {
                tag: 'div',
                className: 'dt-buttons'
            },
            collection: {
                tag: 'div',
                className: 'dt-button-collection'
            },
            button: {
                tag: 'button',
                className: 'dt-button',
                active: 'active',
                disabled: 'disabled'
            },
            buttonLiner: {
                tag: 'span',
                className: ''
            }
        }
    };

    /**
     * Version information
     * @type {string}
     * @static
     */
    Buttons.version = '1.5.2';


    $.extend(_dtButtons, {
        collection: {
            text: function (dt) {
                return dt.i18n('buttons.collection', 'Collection');
            },
            className: 'buttons-collection',
            action: function (e, dt, button, config) {
                var host = button;
                var collectionParent = $(button).parents('div.dt-button-collection');
                var hostPosition = host.position();
                var tableContainer = $(dt.table().container());
                var multiLevel = false;
                var insertPoint = host;

                // Remove any old collection
                if (collectionParent.length) {
                    multiLevel = $('.dt-button-collection').position();
                    insertPoint = collectionParent;
                    $('body').trigger('click.dtb-collection');
                }

                if (insertPoint.parents('body')[0] !== document.body) {
                    insertPoint = document.body.lastChild;
                }

                config._collection
                    .addClass(config.collectionLayout)
                    .css('display', 'none')
                    .insertAfter(insertPoint)
                    .fadeIn(config.fade);


                var position = config._collection.css('position');

                if (multiLevel && position === 'absolute') {
                    config._collection.css({
                        top: multiLevel.top,
                        left: multiLevel.left
                    });
                }
                else if (position === 'absolute') {
                    config._collection.css({
                        top: hostPosition.top + host.outerHeight(),
                        left: hostPosition.left
                    });

                    // calculate overflow when positioned beneath
                    var tableBottom = tableContainer.offset().top + tableContainer.height();
                    var listBottom = hostPosition.top + host.outerHeight() + config._collection.outerHeight();
                    var bottomOverflow = listBottom - tableBottom;

                    // calculate overflow when positioned above
                    var listTop = hostPosition.top - config._collection.outerHeight();
                    var tableTop = tableContainer.offset().top;
                    var topOverflow = tableTop - listTop;

                    // if bottom overflow is larger, move to the top because it fits better, or if dropup is requested
                    if (bottomOverflow > topOverflow || config.dropup) {
                        config._collection.css('top', hostPosition.top - config._collection.outerHeight() - 5);
                    }

                    // Right alignment in table container
                    var listRight = hostPosition.left + config._collection.outerWidth();
                    var tableRight = tableContainer.offset().left + tableContainer.width();
                    if (listRight > tableRight) {
                        config._collection.css('left', hostPosition.left - ( listRight - tableRight ));
                    }

                    // Right alignment to window
                    var listOffsetRight = host.offset().left + config._collection.outerWidth();
                    if (listOffsetRight > $(window).width()) {
                        config._collection.css('left', hostPosition.left - (listOffsetRight - $(window).width()));
                    }
                }
                else {
                    // Fix position - centre on screen
                    var top = config._collection.height() / 2;
                    if (top > $(window).height() / 2) {
                        top = $(window).height() / 2;
                    }

                    config._collection.css('marginTop', top * -1);
                }

                if (config.background) {
                    Buttons.background(true, config.backgroundClassName, config.fade);
                }

                var close = function () {
                    config._collection
                        .fadeOut(config.fade, function () {
                            config._collection.detach();
                        });

                    $('div.dt-button-background').off('click.dtb-collection');
                    Buttons.background(false, config.backgroundClassName, config.fade);

                    $('body').off('.dtb-collection');
                    dt.off('buttons-action.b-internal');
                };

                // Need to break the 'thread' for the collection button being
                // activated by a click - it would also trigger this event
                setTimeout(function () {
                    // This is bonkers, but if we don't have a click listener on the
                    // background element, iOS Safari will ignore the body click
                    // listener below. An empty function here is all that is
                    // required to make it work...
                    $('div.dt-button-background').on('click.dtb-collection', function () {
                    });

                    $('body')
                        .on('click.dtb-collection', function (e) {
                            // andSelf is deprecated in jQ1.8, but we want 1.7 compat
                            var back = $.fn.addBack ? 'addBack' : 'andSelf';

                            if (!$(e.target).parents()[back]().filter(config._collection).length) {
                                close();
                            }
                        })
                        .on('keyup.dtb-collection', function (e) {
                            if (e.keyCode === 27) {
                                close();
                            }
                        });

                    if (config.autoClose) {
                        dt.on('buttons-action.b-internal', function () {
                            close();
                        });
                    }
                }, 10);
            },
            background: true,
            collectionLayout: '',
            backgroundClassName: 'dt-button-background',
            autoClose: false,
            fade: 400,
            attr: {
                'aria-haspopup': true
            }
        },
        copy: function (dt, conf) {
            if (_dtButtons.copyHtml5) {
                return 'copyHtml5';
            }
            if (_dtButtons.copyFlash && _dtButtons.copyFlash.available(dt, conf)) {
                return 'copyFlash';
            }
        },
        csv: function (dt, conf) {
            // Common option that will use the HTML5 or Flash export buttons
            if (_dtButtons.csvHtml5 && _dtButtons.csvHtml5.available(dt, conf)) {
                return 'csvHtml5';
            }
            if (_dtButtons.csvFlash && _dtButtons.csvFlash.available(dt, conf)) {
                return 'csvFlash';
            }
        },
        excel: function (dt, conf) {
            // Common option that will use the HTML5 or Flash export buttons
            if (_dtButtons.excelHtml5 && _dtButtons.excelHtml5.available(dt, conf)) {
                return 'excelHtml5';
            }
            if (_dtButtons.excelFlash && _dtButtons.excelFlash.available(dt, conf)) {
                return 'excelFlash';
            }
        },
        pdf: function (dt, conf) {
            // Common option that will use the HTML5 or Flash export buttons
            if (_dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available(dt, conf)) {
                return 'pdfHtml5';
            }
            if (_dtButtons.pdfFlash && _dtButtons.pdfFlash.available(dt, conf)) {
                return 'pdfFlash';
            }
        },
        pageLength: function (dt) {
            var lengthMenu = dt.settings()[0].aLengthMenu;
            var vals = $.isArray(lengthMenu[0]) ? lengthMenu[0] : lengthMenu;
            var lang = $.isArray(lengthMenu[0]) ? lengthMenu[1] : lengthMenu;
            var text = function (dt) {
                return dt.i18n('buttons.pageLength', {
                    "-1": 'Show all rows',
                    _: 'Show %d rows'
                }, dt.page.len());
            };

            return {
                extend: 'collection',
                text: text,
                className: 'buttons-page-length',
                autoClose: true,
                buttons: $.map(vals, function (val, i) {
                    return {
                        text: lang[i],
                        className: 'button-page-length',
                        action: function (e, dt) {
                            dt.page.len(val).draw();
                        },
                        init: function (dt, node, conf) {
                            var that = this;
                            var fn = function () {
                                that.active(dt.page.len() === val);
                            };

                            dt.on('length.dt' + conf.namespace, fn);
                            fn();
                        },
                        destroy: function (dt, node, conf) {
                            dt.off('length.dt' + conf.namespace);
                        }
                    };
                }),
                init: function (dt, node, conf) {
                    var that = this;
                    dt.on('length.dt' + conf.namespace, function () {
                        that.text(text(dt));
                    });
                },
                destroy: function (dt, node, conf) {
                    dt.off('length.dt' + conf.namespace);
                }
            };
        }
    });


    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * DataTables API
     *
     * For complete documentation, please refer to the docs/api directory or the
     * DataTables site
     */

// Buttons group and individual button selector
    DataTable.Api.register('buttons()', function (group, selector) {
        // Argument shifting
        if (selector === undefined) {
            selector = group;
            group = undefined;
        }

        this.selector.buttonGroup = group;

        var res = this.iterator(true, 'table', function (ctx) {
            if (ctx._buttons) {
                return Buttons.buttonSelector(
                    Buttons.instanceSelector(group, ctx._buttons),
                    selector
                );
            }
        }, true);

        res._groupSelector = group;
        return res;
    });

// Individual button selector
    DataTable.Api.register('button()', function (group, selector) {
        // just run buttons() and truncate
        var buttons = this.buttons(group, selector);

        if (buttons.length > 1) {
            buttons.splice(1, buttons.length);
        }

        return buttons;
    });

// Active buttons
    DataTable.Api.registerPlural('buttons().active()', 'button().active()', function (flag) {
        if (flag === undefined) {
            return this.map(function (set) {
                return set.inst.active(set.node);
            });
        }

        return this.each(function (set) {
            set.inst.active(set.node, flag);
        });
    });

// Get / set button action
    DataTable.Api.registerPlural('buttons().action()', 'button().action()', function (action) {
        if (action === undefined) {
            return this.map(function (set) {
                return set.inst.action(set.node);
            });
        }

        return this.each(function (set) {
            set.inst.action(set.node, action);
        });
    });

// Enable / disable buttons
    DataTable.Api.register(['buttons().enable()', 'button().enable()'], function (flag) {
        return this.each(function (set) {
            set.inst.enable(set.node, flag);
        });
    });

// Disable buttons
    DataTable.Api.register(['buttons().disable()', 'button().disable()'], function () {
        return this.each(function (set) {
            set.inst.disable(set.node);
        });
    });

// Get button nodes
    DataTable.Api.registerPlural('buttons().nodes()', 'button().node()', function () {
        var jq = $();

        // jQuery will automatically reduce duplicates to a single entry
        $(this.each(function (set) {
            jq = jq.add(set.inst.node(set.node));
        }));

        return jq;
    });

// Get / set button processing state
    DataTable.Api.registerPlural('buttons().processing()', 'button().processing()', function (flag) {
        if (flag === undefined) {
            return this.map(function (set) {
                return set.inst.processing(set.node);
            });
        }

        return this.each(function (set) {
            set.inst.processing(set.node, flag);
        });
    });

// Get / set button text (i.e. the button labels)
    DataTable.Api.registerPlural('buttons().text()', 'button().text()', function (label) {
        if (label === undefined) {
            return this.map(function (set) {
                return set.inst.text(set.node);
            });
        }

        return this.each(function (set) {
            set.inst.text(set.node, label);
        });
    });

// Trigger a button's action
    DataTable.Api.registerPlural('buttons().trigger()', 'button().trigger()', function () {
        return this.each(function (set) {
            set.inst.node(set.node).trigger('click');
        });
    });

// Get the container elements
    DataTable.Api.registerPlural('buttons().containers()', 'buttons().container()', function () {
        var jq = $();
        var groupSelector = this._groupSelector;

        // We need to use the group selector directly, since if there are no buttons
        // the result set will be empty
        this.iterator(true, 'table', function (ctx) {
            if (ctx._buttons) {
                var insts = Buttons.instanceSelector(groupSelector, ctx._buttons);

                for (var i = 0, ien = insts.length; i < ien; i++) {
                    jq = jq.add(insts[i].container());
                }
            }
        });

        return jq;
    });

// Add a new button
    DataTable.Api.register('button().add()', function (idx, conf) {
        var ctx = this.context;

        // Don't use `this` as it could be empty - select the instances directly
        if (ctx.length) {
            var inst = Buttons.instanceSelector(this._groupSelector, ctx[0]._buttons);

            if (inst.length) {
                inst[0].add(conf, idx);
            }
        }

        return this.button(this._groupSelector, idx);
    });

// Destroy the button sets selected
    DataTable.Api.register('buttons().destroy()', function () {
        this.pluck('inst').unique().each(function (inst) {
            inst.destroy();
        });

        return this;
    });

// Remove a button
    DataTable.Api.registerPlural('buttons().remove()', 'buttons().remove()', function () {
        this.each(function (set) {
            set.inst.remove(set.node);
        });

        return this;
    });

// Information box that can be used by buttons
    var _infoTimer;
    DataTable.Api.register('buttons.info()', function (title, message, time) {
        var that = this;

        if (title === false) {
            $('#datatables_buttons_info').fadeOut(function () {
                $(this).remove();
            });
            clearTimeout(_infoTimer);
            _infoTimer = null;

            return this;
        }

        if (_infoTimer) {
            clearTimeout(_infoTimer);
        }

        if ($('#datatables_buttons_info').length) {
            $('#datatables_buttons_info').remove();
        }

        title = title ? '<h2>' + title + '</h2>' : '';

        $('<div id="datatables_buttons_info" class="dt-button-info"/>')
            .html(title)
            .append($('<div/>')[typeof message === 'string' ? 'html' : 'append'](message))
            .css('display', 'none')
            .appendTo('body')
            .fadeIn();

        if (time !== undefined && time !== 0) {
            _infoTimer = setTimeout(function () {
                that.buttons.info(false);
            }, time);
        }

        return this;
    });

// Get data from the table for export - this is common to a number of plug-in
// buttons so it is included in the Buttons core library
    DataTable.Api.register('buttons.exportData()', function (options) {
        if (this.context.length) {
            return _exportData(new DataTable.Api(this.context[0]), options);
        }
    });

// Get information about the export that is common to many of the export data
// types (DRY)
    DataTable.Api.register('buttons.exportInfo()', function (conf) {
        if (!conf) {
            conf = {};
        }

        return {
            filename: _filename(conf),
            title: _title(conf),
            messageTop: _message(this, conf.message || conf.messageTop, 'top'),
            messageBottom: _message(this, conf.messageBottom, 'bottom')
        };
    });


    /**
     * Get the file name for an exported file.
     *
     * @param {object}    config Button configuration
     * @param {boolean} incExtension Include the file name extension
     */
    var _filename = function (config) {
        // Backwards compatibility
        var filename = config.filename === '*' && config.title !== '*' && config.title !== undefined && config.title !== null && config.title !== '' ?
            config.title :
            config.filename;

        if (typeof filename === 'function') {
            filename = filename();
        }

        if (filename === undefined || filename === null) {
            return null;
        }

        if (filename.indexOf('*') !== -1) {
            filename = $.trim(filename.replace('*', $('head > title').text()));
        }

        // Strip characters which the OS will object to
        filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, "");

        var extension = _stringOrFunction(config.extension);
        if (!extension) {
            extension = '';
        }

        return filename + extension;
    };

    /**
     * Simply utility method to allow parameters to be given as a function
     *
     * @param {undefined|string|function} option Option
     * @return {null|string} Resolved value
     */
    var _stringOrFunction = function (option) {
        if (option === null || option === undefined) {
            return null;
        }
        else if (typeof option === 'function') {
            return option();
        }
        return option;
    };

    /**
     * Get the title for an exported file.
     *
     * @param {object} config    Button configuration
     */
    var _title = function (config) {
        var title = _stringOrFunction(config.title);

        return title === null ?
            null : title.indexOf('*') !== -1 ?
                title.replace('*', $('head > title').text() || 'Exported data') :
                title;
    };

    var _message = function (dt, option, position) {
        var message = _stringOrFunction(option);
        if (message === null) {
            return null;
        }

        var caption = $('caption', dt.table().container()).eq(0);
        if (message === '*') {
            var side = caption.css('caption-side');
            if (side !== position) {
                return null;
            }

            return caption.length ?
                caption.text() :
                '';
        }

        return message;
    };


    var _exportTextarea = $('<textarea/>')[0];
    var _exportData = function (dt, inOpts) {
        var config = $.extend(true, {}, {
            rows: null,
            columns: '',
            modifier: {
                search: 'applied',
                order: 'applied'
            },
            orthogonal: 'display',
            stripHtml: true,
            stripNewlines: true,
            decodeEntities: true,
            trim: true,
            format: {
                header: function (d) {
                    return strip(d);
                },
                footer: function (d) {
                    return strip(d);
                },
                body: function (d) {
                    return strip(d);
                }
            },
            customizeData: null
        }, inOpts);

        var strip = function (str) {
            if (typeof str !== 'string') {
                return str;
            }

            // Always remove script tags
            str = str.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');

            // Always remove comments
            str = str.replace(/<!\-\-.*?\-\->/g, '');

            if (config.stripHtml) {
                str = str.replace(/<[^>]*>/g, '');
            }

            if (config.trim) {
                str = str.replace(/^\s+|\s+$/g, '');
            }

            if (config.stripNewlines) {
                str = str.replace(/\n/g, ' ');
            }

            if (config.decodeEntities) {
                _exportTextarea.innerHTML = str;
                str = _exportTextarea.value;
            }

            return str;
        };


        var header = dt.columns(config.columns).indexes().map(function (idx) {
            var el = dt.column(idx).header();
            return config.format.header(el.innerHTML, idx, el);
        }).toArray();

        var footer = dt.table().footer() ?
            dt.columns(config.columns).indexes().map(function (idx) {
                var el = dt.column(idx).footer();
                return config.format.footer(el ? el.innerHTML : '', idx, el);
            }).toArray() :
            null;

        // If Select is available on this table, and any rows are selected, limit the export
        // to the selected rows. If no rows are selected, all rows will be exported. Specify
        // a `selected` modifier to control directly.
        var modifier = $.extend({}, config.modifier);
        if (dt.select && typeof dt.select.info === 'function' && modifier.selected === undefined) {
            if (dt.rows(config.rows, $.extend({selected: true}, modifier)).any()) {
                $.extend(modifier, {selected: true})
            }
        }

        var rowIndexes = dt.rows(config.rows, modifier).indexes().toArray();
        var selectedCells = dt.cells(rowIndexes, config.columns);
        var cells = selectedCells
            .render(config.orthogonal)
            .toArray();
        var cellNodes = selectedCells
            .nodes()
            .toArray();

        var columns = header.length;
        var rows = columns > 0 ? cells.length / columns : 0;
        var body = [];
        var cellCounter = 0;

        for (var i = 0, ien = rows; i < ien; i++) {
            var row = [columns];

            for (var j = 0; j < columns; j++) {
                row[j] = config.format.body(cells[cellCounter], i, j, cellNodes[cellCounter]);
                cellCounter++;
            }

            body[i] = row;
        }

        var data = {
            header: header,
            footer: footer,
            body: body
        };

        if (config.customizeData) {
            config.customizeData(data);
        }

        return data;
    };


    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * DataTables interface
     */

// Attach to DataTables objects for global access
    $.fn.dataTable.Buttons = Buttons;
    $.fn.DataTable.Buttons = Buttons;


// DataTables creation - check if the buttons have been defined for this table,
// they will have been if the `B` option was used in `dom`, otherwise we should
// create the buttons instance here so they can be inserted into the document
// using the API. Listen for `init` for compatibility with pre 1.10.10, but to
// be removed in future.
    $(document).on('init.dt plugin-init.dt', function (e, settings) {
        if (e.namespace !== 'dt') {
            return;
        }

        var opts = settings.oInit.buttons || DataTable.defaults.buttons;

        if (opts && !settings._buttons) {
            new Buttons(settings, opts).container();
        }
    });

// DataTables `dom` feature option
    DataTable.ext.feature.push({
        fnInit: function (settings) {
            var api = new DataTable.Api(settings);
            var opts = api.init().buttons || DataTable.defaults.buttons;

            return new Buttons(api, opts).container();
        },
        cFeature: "B"
    });


    return Buttons;
}));
